package manage.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import core.constants.Constant;
import core.enums.ResultCode;
import core.exception.ServiceException;
import manage.domain.exam.Exam;
import manage.domain.exam.ExamQuestion;
import manage.domain.exam.dto.ExamAddDTO;
import manage.domain.exam.dto.ExamEditDTO;
import manage.domain.exam.dto.ExamListDTO;
import manage.domain.exam.dto.ExamQuestionAddDTO;
import manage.domain.exam.vo.ExamDetailVO;
import manage.domain.exam.vo.ExamVO;
import manage.domain.question.Question;
import manage.domain.question.vo.QuestionVO;
import manage.manager.ExamCacheManage;
import manage.mapper.ExamMapper;
import manage.mapper.ExamQuestionMapper;
import manage.mapper.QuestionMapper;
import manage.service.IExamService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements IExamService {
    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManage examCacheManage;

    @Override
    public List<ExamVO> list(ExamListDTO examListDTO) {
        PageHelper.startPage(examListDTO.getPageNum(), examListDTO.getPageSize());
        return examMapper.selectExamList(examListDTO);
    }

    @Override
    public String add(ExamAddDTO examAddDTO) {
        checkExamSaveParams(examAddDTO, null);
        Exam exam = new Exam();
        exam.setTitle(examAddDTO.getTitle());
        exam.setStartTime(examAddDTO.getStartTime());
        exam.setEndTime(examAddDTO.getEndTime());
        examMapper.insert(exam);
        return exam.getExamId().toString();
    }


    @Override
    public boolean addQuestion(ExamQuestionAddDTO examQuestionAddDTO) {
        // 判断当前竞赛是否存在
        Long examId = examQuestionAddDTO.getExamId();
        Exam exam = getExam(examId);
        checkExam(exam);
        if(exam.getStatus() == 1){
            throw new ServiceException(ResultCode.EXAM_PUBLISHED);
        }
        // 判断题目是否全部存在
        Set<Long> questionIdSet = examQuestionAddDTO.getQuestionIdSet();
        if(CollectionUtil.isEmpty(questionIdSet)){
            return false;
        }
        List<Question> questions = questionMapper
                .selectBatchIds(questionIdSet);
        if(CollectionUtil.isEmpty(questions) || questions.size() != questionIdSet.size()){
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }

        List<Long> questionIds = questions.stream().map(Question::getQuestionId).toList();

        // 若存在, 则插入到竞赛所对应的题目中
        int count = 0;
        List<ExamQuestion> questionList = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            if(!questionIds.contains(questionId)){
                throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
            }
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setQuestionId(questionId);
            examQuestion.setExamId(examId);
            examQuestion.setQuestionOrder(++count);
            questionList.add(examQuestion);
            // examQuestionMapper.insert(examQuestion);
        }

        return saveBatch(questionList);
    }

    @Override
    public ExamDetailVO detail(Long examId) {
        ExamDetailVO examDetailVO = new ExamDetailVO();
        Exam exam = getExam(examId);
        BeanUtils.copyProperties(exam, examDetailVO);
        List<ExamQuestion> examQuestionList = examQuestionMapper
                .selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));
        if(CollectionUtil.isEmpty(examQuestionList)) {
            return examDetailVO;
        }

        List<Long> questionIds = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();
        List<Question> questionList = questionMapper
                .selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty)
                .in(Question::getQuestionId, questionIds));
        List<QuestionVO> questionVOList = questionList.stream().map(question -> {
            QuestionVO questionVO = new QuestionVO();
            BeanUtils.copyProperties(question, questionVO);
            return questionVO;
        }).toList();
//        List<QuestionVO> questionVOList = new ArrayList<>();
//        BeanUtils.copyProperties(questionList, questionVOList);
        examDetailVO.setExamQuestionList(questionVOList);
        return examDetailVO;
    }

    @Override
    public int edit(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        checkExam(exam);
        if(exam.getStatus() == 1){
            throw new ServiceException(ResultCode.EXAM_PUBLISHED);
        }
        checkExamSaveParams(examEditDTO, exam.getExamId());
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(exam);
    }

    @Override
    public int deleteQuestion(Long examId, Long questionId) {
        // 判断当前竞赛是否存在，并且是否可以被删除
        Exam exam = getExam(examId);
        if(exam.getStatus() == 1){
            throw new ServiceException(ResultCode.EXAM_PUBLISHED);
        }
        checkExam(exam);
        ExamQuestion examQuestion = examQuestionMapper.selectOne(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
        if(examQuestion == null) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }
        return examQuestionMapper
                .delete(new LambdaUpdateWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
    }

    @Override
    public int delete(Long examId) {
        Exam exam = getExam(examId);
        checkExam(exam);
        if(exam.getStatus() == 1){
            throw new ServiceException(ResultCode.EXAM_PUBLISHED);
        }
        examQuestionMapper.delete(new LambdaUpdateWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        return examMapper.deleteById(examId);
    }

    @Override
    public int publish(Long examId) {

//        Exam exam = getExam(examId);
//        Long count = examQuestionMapper
//                .selectCount(new LambdaQueryWrapper<ExamQuestion>()
//                .eq(ExamQuestion::getExamId, examId));
//        if(count == null || count <= 0) {
//            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
//        }
//        exam.setStatus(Constant.TRUE);
//        return examMapper.updateById(exam);
        // Redis 中 list 数据结构
        // 1. key: ing_exam_list value(list): examId 未完善的竞赛
        // 2. key: ed_exam_list value(list): examId 已结束的竞赛

        Exam exam = getExam(examId);
        if(exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_FINISHED);
        }

        Long count = examQuestionMapper
                .selectCount(new LambdaQueryWrapper<ExamQuestion>()
                        .eq(ExamQuestion::getExamId, examId));
        if(count == null || count <= 0) {
            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }
        if(exam.getStatus().equals(Constant.TRUE)) { // 竞赛已经已发布
            return 0;
        }
        exam.setStatus(Constant.TRUE);

        examCacheManage.addCache(exam);
        return examMapper.updateById(exam);
    }

    @Override
    public int unPublish(Long examId) {
        Exam exam = getExam(examId);
        if(exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_FINISHED);
        }
        examCacheManage.deleteCache(exam);
        checkExam(exam);
        exam.setStatus(Constant.FALSE);
        return examMapper.updateById(exam);
    }

    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
        return exam;
    }


    private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
        if(examSaveDTO.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_NOW);
        }
        if(examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_START_TIME);
        }
        Exam title = examMapper.selectOne(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                .ne(examId != null, Exam::getExamId, examId));
        if (title != null) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
    }

    public void checkExam(Exam exam){
        if(exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }
}
